#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <chrono>
#include <thread>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include "ShaderSource.h"
#include <math.h>
#include <SOIL/SOIL.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

using namespace std;
using namespace glm;


void printMatrix(mat4 mat){
    for(int i = 0; i < 4; i++){
        for(int j = 0; j < 4; j++){
            printf("%.2f ", mat[i][j]);
        }
        printf("\n");
    }
    printf("\n");
}

int main()
{
    glewExperimental = GL_TRUE;

    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    GLFWwindow * wwindow = glfwCreateWindow(1600, 960, "FUCK ME!", nullptr, nullptr);
    glfwMakeContextCurrent(wwindow);

    // glew init and GenBuffers must be put after the window creation.
    glewInit();
//    GLuint vertexBuffer=0;
//    glGenBuffers(0, &vertexBuffer);

    // make vertex array object
    GLuint vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    // prepare data
    float vertices[] = {
            // position      color              shader
            -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
            0.5f,  -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
            0.5f,   0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
            0.5f,   0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
            -0.5f,  0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
            -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,

            -0.5f, -0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
            0.5f,  -0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
            0.5f,   0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
            0.5f,   0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
            -0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
            -0.5f, -0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,

            -0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
            -0.5f,  0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
            -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
            -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
            -0.5f, -0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
            -0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,

            0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
            0.5f,  0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
            0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
            0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
            0.5f, -0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
            0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,

            -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
            0.5f,  -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
            0.5f,  -0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
            0.5f,  -0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
            -0.5f, -0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
            -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,

            -0.5f,  0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
            0.5f,   0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
            0.5f,   0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
            0.5f,   0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
            -0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
            -0.5f,  0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f
    };
    GLuint vbo;
    glGenBuffers(1, &vbo); // Generate 1 buffer
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);


    // making vertex shader and fragment shader shaders
    ShaderProgram sp;
    sp.Init();
    sp.addShaderFromFile("vertex.glsl", GL_VERTEX_SHADER);
    sp.addShaderFromFile("fragment.glsl", GL_FRAGMENT_SHADER);

    glBindFragDataLocation(sp,0,"outColor");
    glLinkProgram(sp);
    glUseProgram(sp);

    // bind in and out parameters
    GLint posAttrib = glGetAttribLocation(sp, "position");
    glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE,
            8*sizeof(float), 0);
    glEnableVertexAttribArray(posAttrib);


    // GLint colorAttrib = glGetAttribLocation(sp, "color");
    // glVertexAttribPointer(colorAttrib, 3, GL_FLOAT, GL_FALSE,
    //         7*sizeof(float), (void*)(2 * sizeof(float)));
    // glEnableVertexAttribArray(colorAttrib);

    // GLint texAttrib = glGetAttribLocation(sp, "texcoord");
    // glEnableVertexAttribArray(texAttrib);
    // glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE,
    //                       8*sizeof(float), (void*)(6*sizeof(float)));

    // bind uniform parameters
    // GLint uniColor = glGetUniformLocation(sp, "triangleColor");
    // glUniform3f(uniColor, 1.0f, 0.4f, 1.0f);

    // GLint uniTime = glGetUniformLocation(sp, "time");
    // glUniform1f(uniColor, 1.0f);

    // make element array buffer
    GLuint elements[] = {
            0, 1, 2,
            3, 4, 5
    };
    GLuint ebo;
    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,
                 sizeof(elements), elements, GL_STATIC_DRAW);


    // // make texture buffer
    // GLuint tex[10];
    // glGenTextures(2, tex);

    // // active texture to generate make fucking operation on this
    // glActiveTexture(GL_TEXTURE0);
    // glBindTexture(GL_TEXTURE_2D, tex[0]);
    // // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    // // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // float color[] = { 1.0f, 0.0f, 0.0f, 1.0f };
    // // glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color);

    // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // glUniform1i(glGetUniformLocation(sp, "tex"), 0);
    // // glGenerateMipmap(GL_TEXTURE_2D);
    // int width, height;
    // unsigned char* image = SOIL_load_image(
    //         "/home/d/Pictures/9zpVhD7.png",
    //         &width, &height, 0, SOIL_LOAD_RGB);
    // glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB,
    //              GL_UNSIGNED_BYTE, image);
    // printf("width %d, height %d\n", width, height);

    // SOIL_free_image_data(image);


    // glActiveTexture(GL_TEXTURE1);
    // glBindTexture(GL_TEXTURE_2D, tex[1]);
    // image = SOIL_load_image(
    //         "/home/d/Pictures/Jc6IFVV.png",
    //         &width, &height, 0, SOIL_LOAD_RGB);
    // glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB,
    //              GL_UNSIGNED_BYTE, image);
    // printf("width %d, height %d\n", width, height);
    // glUniform1i(glGetUniformLocation(sp, "tex2"), 1);
    // glGenerateMipmap(GL_TEXTURE_2D);
    // SOIL_free_image_data(image);


    // transformation
    GLint uniModel = glGetUniformLocation(sp, "model");
    glm::mat4 trans(1);
    trans = glm::rotate(trans, glm::radians(0.0f), vec3(0.0f, 0.0f, 1.0f));
    glUniformMatrix4fv(uniModel, 1, GL_FALSE, glm::value_ptr(trans));

    // view transformation
    GLint uniView = glGetUniformLocation(sp, "view");
    glm::mat4 view = glm::lookAt(
            glm::vec3(1.2f, 1.2f, 1.2f),
            glm::vec3(0.0f, 0.0f, 0.0f),
            glm::vec3(0.0f, 0.0f, 1.0f)
    );
    glUniformMatrix4fv(uniView, 1, GL_FALSE, glm::value_ptr(view));

    // projection matrix
    GLint uniProj = glGetUniformLocation(sp, "proj");
    glm::mat4 proj = glm::perspective(
            glm::radians(45.0f), 800.0f / 600.0f, 1.0f, 10.0f);
    glUniformMatrix4fv(uniProj, 1, GL_FALSE, glm::value_ptr(proj));


    GLuint err = glGetError();
    if(err != 0){
        printf("ERROR FUCK! %d\n", err);
        char buffer[512];
        glGetShaderInfoLog(err, 512, nullptr, buffer);
        printf("Compile log:%s\n", buffer);
    } else {
        printf("good\n");
    }
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    auto t_start = std::chrono::high_resolution_clock::now();
    while (!glfwWindowShouldClose(wwindow)){
        // glEnable(GL_DEPTH_TEST);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        auto t_now = std::chrono::high_resolution_clock::now();
        float time = std::chrono::duration_cast<std::chrono::duration<float> >
                (t_now - t_start).count();
        // glUniform1f(uniTime,  time);
        view = glm::lookAt(
                glm::vec3(1.2f, 1.2f, 1.2f),
                glm::vec3(0.0f, 0.0f, 0.0f),
                glm::vec3(0.0f, 0.0f, 1.0f)
        );
        glUniformMatrix4fv(uniView, 1, GL_FALSE, glm::value_ptr(view));

         // glDrawArrays(GL_TRIANGLES, 0, 36);
        // use draw arrays here instead of draw elements
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        // if esc key is pressed, then exit
        if(glfwGetKey(wwindow, GLFW_KEY_ESCAPE) == GLFW_PRESS){
            glfwSetWindowShouldClose(wwindow, true);
        }
        // swap buffers, and show the newly drown things.
        glfwSwapBuffers(wwindow);
        // swallow the not handled events.
        glfwPollEvents();
    }
    glfwTerminate();
    return 0;
}
